The Simplest Possible Delegate Example

To be sure, delegates can cause a great deal of confusion when encountered for the first time. Thus, to get the ball rolling, let’s take a look at a very simple Console Application program (named SimpleDelegate) that makes use of the BinaryOp delegate type you’ve seen previously. Here is the complete code, with analysis to follow:

namespace SimpleDelegate
{
    // This delegate can point to any method,
    // taking two integers and returning an integer.
    public delegate int BinaryOp(int x, int y);

    // This class contains methods BinaryOp will
    // point to.
    public class SimpleMath
    {
        public static int Add(int x, int y)
        { return x + y; }
        public static int Subtract(int x, int y)
        { return x - y; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***** Simple Delegate Example *****\n");
        
            // Create a BinaryOp delegate object that
            // "points to" SimpleMath.Add().
            BinaryOp b = new BinaryOp(SimpleMath.Add);

            // Invoke Add() method indirectly using delegate object.
            Console.WriteLine("10 + 10 is {0}", b(10, 10));
            Console.ReadLine();
        }
    }
}

Again, notice the format of the BinaryOp delegate type declaration; it specifies that BinaryOp delegate objects can point to any method taking two integers and returning an integer (the actual name of the method pointed to is irrelevant). Here, we have created a class named SimpleMath, which defines two static methods that (surprise, surprise) match the pattern defined by the BinaryOp delegate.

When you want to insert the target method to a given delegate object, simply pass in the name of the method to the delegate’s constructor:

// Create a BinaryOp delegate object that
// "points to" SimpleMath.Add().
BinaryOp b = new BinaryOp(SimpleMath.Add);

At this point, you are able to invoke the member pointed to using a syntax that looks like a direct function invocation:

// Invoke() is really called here!
Console.WriteLine("10 + 10 is {0}", b(10, 10));

Under the hood, the runtime actually calls the compiler-generated Invoke() method on your MulticastDelegate derived class. You can verify this for yourself if you open your assembly in ildasm.exe and examine the CIL code within the Main() method:

.method private hidebysig static void Main(string[] args) cil managed
{
...
    callvirt instance int32 SimpleDelegate.BinaryOp::Invoke(int32, int32)
}

Although C# does not require you to explicitly call Invoke() within your code base, you are free to do so. Thus, the following code statement is also permissible:

Console.WriteLine("10 + 10 is {0}", b.Invoke(10, 10));

Recall that .NET delegates are type safe. Therefore, if you attempt to pass a delegate a method that does not match the pattern, you receive a compile-time error. To illustrate, assume the SimpleMath class now defines an additional method named SquareNumber(), which takes a single integer as input:

public class SimpleMath
{
...
    public static int SquareNumber(int a)
    { return a * a; }
}

Given that the BinaryOp delegate can only point to methods that take two integers and return an integer, the following code is illegal and will not compile:

// Compiler error! Method does not match delegate pattern!
BinaryOp b2 = new BinaryOp(SimpleMath.SquareNumber);

Investigating a Delegate Object

Let’s spice up the current example by creating a static method (named DisplayDelegateInfo()) within the Program class. This method will print out the names of the method(s) maintained by a delegate object as well as the name of the class defining the method. To do this, we will iterate over the System.Delegate array returned by GetInvocationList(), invoking each object’s Target and Method properties:

static void DisplayDelegateInfo(Delegate delObj)
{
    // Print the names of each member in the
    // delegate's invocation list.
    foreach (Delegate d in delObj.GetInvocationList())
    {
        Console.WriteLine("Method Name: {0}", d.Method);
        Console.WriteLine("Type Name: {0}", d.Target);
    }
}

Assuming you have updated your Main() method to actually call this new helper method, you would find the output shown below:

***** Simple Delegate Example *****

Method Name: Int32 Add(Int32, Int32)
Type Name:
10 + 10 is 20

Notice that the name of the type (SimpleMath) is currently not displayed when calling the Target property. The reason has to do with the fact that our BinaryOp delegate is pointing to a static method and therefore there is no object to reference! However, if we update the Add() and Subtract() methods to be nonstatic (simply by deleting the static keywords), we could create an instance of the SimpleMath class and specify the methods to invoke using the object reference:

static void Main(string[] args)
{
    Console.WriteLine("***** Simple Delegate Example *****\n");
    
    // .NET delegates can also point to instance methods as well.
    SimpleMath m = new SimpleMath();
    BinaryOp b = new BinaryOp(m.Add);

    // Show information about this object.
    DisplayDelegateInfo(b);

    Console.WriteLine("10 + 10 is {0}", b(10, 10));
    Console.ReadLine();
}

In this case, we would find the output shown here.

***** Simple Delegate Example *****

Method Name: Int32 Add(Int32, Int32)
Type Name: SimpleDelegate.SimpleMath
10 + 10 is 20

Source Code The SimpleDelegate project is located under the Chapter 11 subdirectory.